﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Net;
using System.Windows;
using System.Windows.Browser;

namespace SilverQuery
{
	/// <summary>
	/// The SilverQueryBridge acts as the intermediary between the JavaScript running in the browser 
	/// and the SilverQuery framework running in Silverlight.
	/// </summary>
	public sealed class SilverQueryBridge : FrameworkElement
	{
		// Lookup of the handler method delegates.
		private static Dictionary<string, Callback> _callbacks = new Dictionary<string, Callback>();

		private static ScriptObject _silverQueryScriptObject;
		private SilverQueryComponent _component;

		/// <summary>
		/// Instantiate the bridge specifying the component to load.
		/// </summary>
		/// <param name="component"></param>
		public SilverQueryBridge(SilverQueryComponent component, IDictionary<string, string> initParams)
		{
			_component = component;
			if (initParams == null)
				initParams = new Dictionary<string, string>();
			_component.InitParameters = initParams;
			
			this.Loaded += new RoutedEventHandler(OnLoaded);
		}

		/// <summary>
		/// Instantiate the bridge using the component type specified in the initParams.
		/// </summary>
		/// <param name="initParams"></param>
		/// <remarks>
		/// This construtor is called when the host page dynamically declares which component to load.
		/// </remarks>
		public SilverQueryBridge(IDictionary<string, string> initParams)
		{
			// Invoke GetCallingAssembly from here so it reflects the App.xml.cs of the host Silverlight control
			// and not the SilverQuery assembly.
			_component = CreateComponent(Assembly.GetCallingAssembly(), initParams);
			
			if (_component == null)
				throw new ArgumentException("The initParams does not contain a 'component' key that specifies the full type name of a valid SilverQueryComponent type.");
			
			_component.InitParameters = initParams;
		
			this.Loaded += new RoutedEventHandler(OnLoaded);
		}

		[ScriptableMember()]
		public void ready()
		{
			_component.ready();
		}
		
		[ScriptableMember()]
		public bool TriggerEventCallback(string callbackId)
		{
			var scriptCallbackRegistry = SilverQueryBridge.GetScriptProperty("eventRegistry");
			var nativeEvent = (ScriptObject)scriptCallbackRegistry.GetProperty(callbackId);

			EventCallback callback = EventCallbackRegistry.Get(callbackId);
			if (callback != null)
				callback(Event.Create(nativeEvent));

			return true;
		}

		[ScriptableMember()]
		public void TriggerCallback(string callbackId)
		{
			Callback callback;
			if (_callbacks.TryGetValue(callbackId, out callback))
				callback();
		}

		private void OnLoaded(object sender, RoutedEventArgs e)
		{
			string contentKey = "content_" + this.GetHashCode();

			// Generate a random key for this component.
			HtmlPage.RegisterScriptableObject(contentKey, this);

			_silverQueryScriptObject = (ScriptObject)HtmlPage.Window.GetProperty("SilverQuery");
			InvokeScriptFunction("registerComponent", contentKey);
		}

		internal static object InvokeScriptFunction(string name, params object[] arguments)
		{
			object retValue = _silverQueryScriptObject.Invoke(name, arguments);
			return retValue;
		}

		internal static ScriptObject GetScriptProperty(string name)
		{
			return (ScriptObject)_silverQueryScriptObject.GetProperty(name);
		}

		/// <summary>
		/// Create a SilverQueryComponent using the type specified in the "component" key
		/// of the InitParameters.  This method is generally called from the 
		/// Application_StartUp method in App.xaml.cs.
		/// </summary>
		/// <param name="initParams"></param>
		/// <param name="component"></param>
		/// <returns></returns>
		private static SilverQueryComponent CreateComponent(Assembly callingAssembly, IDictionary<string, string> initParams)
		{
			string componentType = null;
			if (initParams.TryGetValue("component", out componentType))
			{
				Type type = callingAssembly.GetType(componentType, false);

				if (type != null && type.IsSubclassOf(typeof(SilverQueryComponent)))
				{
					var component = (SilverQueryComponent)Activator.CreateInstance(type);
					component.InitParameters = initParams;
					return component;
				}
			}
			return null;
		}

		internal static ScriptObject GetScriptEventCallback(EventCallback callback)
		{
			if (callback == null)
				return null;

			ScriptObject scriptFunc;
			scriptFunc = EventCallbackRegistry.GetScriptFunction(callback);
			if (scriptFunc == null)
			{
				// Create a new unique identifier for this event handler.
				var callbackId = Guid.NewGuid().ToString();
				scriptFunc = (ScriptObject)SilverQueryBridge.InvokeScriptFunction("createEventCallback", callbackId);
				EventCallbackRegistry.Add(callbackId, callback, scriptFunc);
			}
			return scriptFunc;
		}

		internal static ScriptObject GetScriptCallback(Callback callback)
		{
			if (callback == null)
				return null;

			var callbackId = Guid.NewGuid().ToString();
			_callbacks[callbackId] = callback;

			return (ScriptObject)SilverQueryBridge.InvokeScriptFunction("createCallback", callbackId);
		}
	}
}
